/* Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include <openssl/x509v3.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/bio.h>
#include "common.h"
#include "certificate/certificate_parser.h"

static tuple<int32_t, string> get_name_str_from_entry(X509_NAME *name)
{
#define NID_MAP_COUNT 7
    int32_t nid;
    X509_NAME_ENTRY *name_entry = nullptr;
    ASN1_STRING *entry_data = nullptr;
    CertInfo cert_info;

    NidMap nid_map[NID_MAP_COUNT] = {
        {NID_countryName,            cert_info.country,          CERT_ITEM_LEN_2},
        {NID_stateOrProvinceName,    cert_info.state,            CERT_ITEM_LEN_128},
        {NID_localityName,           cert_info.location,         CERT_ITEM_LEN_128},
        {NID_organizationName,       cert_info.organization,     CERT_ITEM_LEN_64},
        {NID_organizationalUnitName, cert_info.organizationUnit, CERT_ITEM_LEN_64},
        {NID_commonName,             cert_info.commomName,       CERT_ITEM_LEN_64},
        {NID_pkcs9_emailAddress,     cert_info.email,            CERT_ITEM_LEN_128},
    };

    int32_t numd = X509_NAME_entry_count(name);
    for (uint8_t i = 0; i < numd; i++) {
        name_entry = X509_NAME_get_entry(name, i);
        if (name_entry == nullptr) {
            return {RET_ERR, ""};
        }

        nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(name_entry));
        entry_data = X509_NAME_ENTRY_get_data(name_entry);
        if (entry_data == nullptr || entry_data->data == nullptr) {
            return {RET_ERR, ""};
        }

        for (uint8_t j = 0; j < NID_MAP_COUNT; j++) {
            if (nid != nid_map[j].nidKey) {
                continue;
            }

            strncpy_s(nid_map[j].pcert_point, nid_map[j].pcert_point_size, (char *)entry_data->data,
                nid_map[j].pcert_point_size - 1);
            break;
        }
    }

    char buffer[CERT_INFO_LEN] = "";
    int32_t ret = snprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, "CN=%s, OU=%s, O=%s, L=%s, S=%s, C=%s",
        cert_info.commomName, cert_info.organizationUnit, cert_info.organization,
        cert_info.location, cert_info.state, cert_info.country);
    if (ret <= 0) {
        debug_log(DLOG_ERROR, "snprintf_s failed, ret = %d", ret);
    }

    return {RET_OK, string(buffer)};
}

static tuple<int32_t, string> get_subject_name(X509 *cert)
{
    X509_NAME *subject_name = X509_get_subject_name(cert);
    return get_name_str_from_entry(subject_name);
}

static tuple<int32_t, string> get_issuer_name(X509 *cert)
{
    X509_NAME *issuer_name = X509_get_issuer_name(cert);
    return get_name_str_from_entry(issuer_name);
}

static int32_t get_sert_sn(X509 *cert, string *serial_number)
{
    string buffer = "";
    char tmp_str[5] = {0};

    ASN1_INTEGER *serial = X509_get_serialNumber(cert);
    if (serial == nullptr) {
        debug_log(DLOG_ERROR, "can not get serial number");
        return RET_ERR;
    }

    for (int32_t i = 0; i < serial->length; i++) {
        (void)memset_s(tmp_str, sizeof(tmp_str), 0, sizeof(tmp_str));
        if (i == serial->length - 1) {
            (void)snprintf_s(tmp_str, sizeof(tmp_str), sizeof(tmp_str) - 1, "%02x", serial->data[i]);
        } else {
            (void)snprintf_s(tmp_str, sizeof(tmp_str), sizeof(tmp_str) - 1, "%02x ", serial->data[i]);
        }

        buffer.append(tmp_str);
    }

    *serial_number = buffer;
    return RET_OK;
}

static void get_cert_validity(X509 *cert, int64_t *valid_not_before, int64_t *valid_not_after)
{
    ASN1_TIME *ptime = nullptr;

    ptime = X509_get_notBefore(cert);
    *valid_not_before = GetAsnDate(ptime);

    ptime = X509_get_notAfter(cert);
    *valid_not_after = GetAsnDate(ptime);

    return;
}

#define MAX_KEY_USAGE_COUNT 9

const static KeyUsageMap key_usage_map[MAX_KEY_USAGE_COUNT] = {
    {KU_DIGITAL_SIGNATURE, "Digital Signature"},
    {KU_NON_REPUDIATION,   "Non Repudiation"},
    {KU_KEY_ENCIPHERMENT,  "Key Encipherment"},
    {KU_DATA_ENCIPHERMENT, "Data Encipherment"},
    {KU_KEY_AGREEMENT,     "Key Aggrement"},
    {KU_KEY_CERT_SIGN,     "Certificate Sign"},
    {KU_CRL_SIGN,          "CRL Sign"},
    {KU_ENCIPHER_ONLY,     "Encipher Only"},
    {KU_DECIPHER_ONLY,     "Decipher Only"}
};

static tuple<uint32_t, string> get_cert_key_usage(X509 *cert)
{
    ASN1_BIT_STRING *key_usage_str = (ASN1_BIT_STRING *)X509_get_ext_d2i(cert, NID_key_usage, nullptr, nullptr);
    if (key_usage_str == nullptr) {
        // 部分证书可能不存在keyUsage
        debug_log(DLOG_INFO, "get key usage string failed");
        return {0, ""};
    }
    ASN1_BIT_STRING_free(key_usage_str);

    uint32_t usage = X509_get_key_usage(cert);
    if (usage == 0) {
        debug_log(DLOG_ERROR, "no key usage data");
        return {0, ""};
    }

    string key_usage = "";

    for (uint8_t i = 0; i < MAX_KEY_USAGE_COUNT; i++) {
        if ((usage & key_usage_map[i].flag) == 0) {
            continue;
        }

        key_usage.append(key_usage_map[i].context).append(", ");
    }

    // 当length大于2时，说明由KeyUsage加入，此时需要将末尾的逗号和空格去掉
    if (key_usage.length() > 2) {
        key_usage.resize(key_usage.length() - 2);
    }

    return {usage, key_usage};
}

static string get_cert_signature_algorithm(X509 *cert)
{
    const X509_ALGOR *alg;
    char algo_str[CERT_ITEM_LEN_64] = {};

    X509_get0_signature(nullptr, &alg, cert);
    OBJ_obj2txt(algo_str, CERT_ITEM_LEN_64, alg->algorithm, 0);

    return string(algo_str);
}

static uint32_t get_cert_key_length(X509 *cert)
{
    EVP_PKEY *pk = X509_get0_pubkey(cert);
    
    uint32_t key_lentgh = EVP_PKEY_bits(pk);

    return key_lentgh;
}

static string get_cert_hash(X509 *cert)
{
    uint64_t hash_val = X509_subject_name_hash(cert);

    char hash_str[CERT_ITEM_LEN_64] = {};

    int32_t ret = snprintf_truncated_s(hash_str, CERT_ITEM_LEN_64, "%08lx", hash_val);
    if (ret < 0) {
        debug_log(DLOG_ERROR, "truncate cert hash failed");
    }

    return ret < 0 ? "" : string(hash_str);
}

static tuple<string, string> get_cert_fingerprint(X509 *cert, string *algo, string *fingerprint)
{
    char finger_print[CERT_ITEM_LEN_64] = {0};
    unsigned char md[CERT_ITEM_LEN_64]  = {0};
    uint32_t md_len                     = 0;

    int32_t ret = X509_digest(cert, EVP_sha256(), md, &md_len);
    if (ret != 1) {
        debug_log(DLOG_ERROR, "get cert fignerprint failed");
        return {"", ""};
    }

    for (uint32_t i = 0; i < md_len; i++) {
        ret = snprintf_s(finger_print + strlen(finger_print), CERT_ITEM_LEN_64 - strlen(finger_print),
            CERT_ITEM_LEN_64 - strlen(finger_print) - 1, "%02x", md[i]);
        if (ret <= 0) {
            debug_log(DLOG_ERROR, "snprintf_s fingerprint failed, ret : %d", ret);
            (void)memset_s(finger_print, CERT_ITEM_LEN_64, 0, CERT_ITEM_LEN_64);
            break;
        }
    }

    return {"TCG_ALG_SHA256", string(finger_print)};
}

static string der_to_pem(X509 *cert)
{
    BIO *bio = BIO_new(BIO_s_mem());
    if (bio == nullptr) {
        debug_log(DLOG_ERROR, "failed to create bio: %s", ERR_error_string(ERR_get_error(), nullptr));
        return "";
    }

    if (PEM_write_bio_X509(bio, cert) != 1) {
        debug_log(DLOG_ERROR, "failed to write cert data: %s", ERR_error_string(ERR_get_error(), nullptr));
        BIO_free(bio);
        return "";
    }

    char *pem_data;
    int64_t pem_len = BIO_get_mem_data(bio, &pem_data);

    BIO_free(bio);

    return string(pem_data, pem_len);
}

tuple<int32_t, CertificateInfo> get_cert_info(const uint8_t *der_data, size_t der_size)
{
    CertificateInfo info;
    X509 *cert = d2i_X509(nullptr, &der_data, der_size);

    if (cert == nullptr) {
        debug_log(DLOG_ERROR, "failed to parse certificate: %s", ERR_error_string(ERR_get_error(), nullptr));
        return {RET_ERR, info};
    }

    auto [sub_res, subject] = get_subject_name(cert);
    if (sub_res != RET_OK) {
        debug_log(DLOG_ERROR, "get cert subject info failed");
        X509_free(cert);
        return {RET_ERR, info};
    }
    info.subject = subject;

    auto [iss_res, issuer] = get_issuer_name(cert);
    if (iss_res != RET_OK) {
        debug_log(DLOG_ERROR, "get cert issuer info failed");
        X509_free(cert);
        return {RET_ERR, info};
    }
    info.issuer = issuer;

    int32_t ret = get_sert_sn(cert, &(info.serialNumber));
    if (ret != RET_OK) {
        debug_log(DLOG_ERROR, "get cert serial number failed");
        X509_free(cert);
        return {RET_ERR, info};
    }

    get_cert_validity(cert, &(info.validNotBefore), &(info.validNotAfter));
    auto [keyUsage, keyUsageString] = get_cert_key_usage(cert);
    info.keyUsage = keyUsage;
    info.keyUsageString = keyUsageString;
    info.signatureAlgorithm = get_cert_signature_algorithm(cert);
    info.keyLength = get_cert_key_length(cert);
    info.hashVal = get_cert_hash(cert);
    auto [algo, fingerprint] = get_cert_fingerprint(cert, &(info.fingerprintAlgorithm), &(info.fingerprint));
    info.fingerprintAlgorithm = algo;
    info.fingerprint = fingerprint;
    info.publicKey = der_to_pem(cert);

    X509_free(cert);
    return {RET_OK, info};
}